package com.white.framework.flow;

import com.alibaba.fastjson.JSON;
import com.white.framework.flow.entity.ExecuteResult;
import com.white.framework.flow.enums.FlowNodeType;
import com.white.framework.flow.enums.FlowOptionEnum;
import com.white.framework.flow.exception.FlowNodeJobBusinessException;
import com.white.framework.flow.service.ThereIsNoTransactionControlNoResultService;
import com.white.framework.flow.utils.FlowNodeJobUtils;
import com.white.framework.flow.utils.RedisDistributedLockUtils;
import com.white.framework.flow.utils.RefType;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.UUID;

/**
 * 一组对流程节点控制的抽象层
 * <p>
 * 封装了对流程引擎的调用
 * 流程业务实现者只需要关系业务逻辑，无需关心流程上下节点是什么
 *
 * @param <T> 流程节点类型
 *            {@link com.white.framework.flow.enums.FlowNodeType} T 类型需要实现接口
 * @param <D> 执行这个Job所需的额外参数 执行器的额外参数
 *            {@link com.white.framework.flow.FlowNodeJobExecute}
 * @param <R> 这个Job执行后当前节点的返回结果，结果是通过，驳回，拒绝。三个操作结果执行后的返回值
 * @param <M> 这个执行器执行后的返回结果集，将作为参数传入通过，驳回，拒绝。三个操作中
 * @author white
 * @create 2020-11-30 16:54
 * @see com.white.framework.flow.FlowNodeJobExecute#execute(String businessNo, D businessDTO)
 */
@Slf4j
public abstract class AbstractFlowNodeJob<T extends FlowNodeType, D, R, M> implements
        FlowNodeJob<D, R>,
        FlowNodeJobExecute<D, M>,
        FlowNodeJobResultProcessing<T,R,M>,
        FlowNodeJobManualNodeVerification ,
        FlowNodeJobExceptionHandler,
        FlowNodeJobJump<T> {

    public static final String SYSTEM = "SYSTEM";

    @Autowired(required = false)
    private TransactionTemplate transactionTemplate;

    @Autowired
    private RedisDistributedLockUtils redisDistributedLockUtils;

    @Autowired
    protected ThereIsNoTransactionControlNoResultService thereIsNoTransactionControlNoResultService;

    /**
     * 全自动执行
     * 完整的执行这个Job
     * 他包含了这个Job的所有生命周期的处理
     *
     * @param businessNo 业务单号
     */
    @Override
    @Transactional
    public R fullyAutomaticRun(String businessNo, D businessDTO) {
        //加分布式锁
        String requestId = UUID.randomUUID().toString().replace("-", "");
        tryGetDistributedLock(String.valueOf(businessNo) + getCurrentFlowNode().getNodeCode(), requestId);
        log.info("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 开始执行节点执行器", getRefType(), getCurrentFlowNode().getNodeName(), businessNo);
        try {
            //节点校验,校验当前执行的Job的节点与业务单号实际的节点是否一致
            if (!checkCurrentFlowNode(businessNo)) {
                throw new FlowNodeJobBusinessException("当前节点不正确");
            }
            //执行节点任务
            ExecuteResult<M> executeResult = null;
            R result;
            try {
                executeResult = execute(businessNo, businessDTO);
                log.info("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 节点执行器执行成功 , 执行结果: {}", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo, JSON.toJSONString(executeResult));
                //跳转到其他节点
                if (executeResult != null && executeResult.getOption() == FlowOptionEnum.REFUSE) {
                    //驳回
                    result = refuse(businessNo, executeResult.getOperatorId(),executeResult.getOperatorName(),executeResult.getRemarks(),executeResult.getData());
                } else if (executeResult != null && executeResult.getOption() == FlowOptionEnum.REJECT) {
                    //拒绝
                    result = reject(businessNo, executeResult.getOperatorId(),executeResult.getOperatorName(),executeResult.getRemarks(),executeResult.getData());
                } else if (executeResult != null && executeResult.getOption() == FlowOptionEnum.PASS){
                    //通过
                    result = pass(businessNo, executeResult.getOperatorId(),executeResult.getOperatorName(),executeResult.getRemarks(),executeResult.getData());
                } else {
                    //没有明确的指令标识，不继续执行，暂停在当前节点
                    result = null;
                }
            } catch (Throwable e) {
                //非事务方式执行异常处理器
                exceHandler(businessNo,e);
                throw e;
            }
            return result;
        } catch (FlowNodeJobBusinessException e) {
            log.error("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 执行节点执行器时失败", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo, e);
            throw e;
        } catch (Throwable e) {
            //其他异常
            log.error("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 执行节点执行器时失败", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo, e);
            throw new FlowNodeJobBusinessException("节点执行失败");
        } finally {
            //释放锁
            releaseDistributedLock(businessNo + getCurrentFlowNode().getNodeCode(), requestId);
        }
    }

    /**
     * 半自动执行
     *
     * @param option       操作结果
     * @param businessNo   业务单号
     * @param operatorId   操作员ID
     * @param operatorName 操作员名称
     * @param remarks      备注
     */
    @Override
    @Transactional
    public R semiAutomaticRun(FlowOptionEnum option, String businessNo, String operatorId, String operatorName, String remarks) {
        //加分布式锁
        String requestId = UUID.randomUUID().toString().replace("-", "");
        tryGetDistributedLock(String.valueOf(businessNo) + getCurrentFlowNode().getNodeCode(), requestId);
        try {
            //节点校验,校验当前执行的Job的节点与业务单号实际的节点是否一致
            if (!checkCurrentFlowNode(businessNo)) {
                throw new FlowNodeJobBusinessException("当前节点不正确");
            }
            switch (option) {
                case PASS:
                    return pass(businessNo,operatorId,operatorName,remarks,null);
                case REJECT:
                    return reject(businessNo,operatorId,operatorName,remarks,null);
                case REFUSE:
                    return refuse(businessNo,operatorId,operatorName,remarks,null);
            }
            throw new FlowNodeJobBusinessException("操作结果参数不正确");
        } finally {
            //释放锁
            releaseDistributedLock(businessNo + getCurrentFlowNode().getNodeCode(), requestId);
        }
    }

    /**
     * 异常处理器统一方法
     * @param businessNo 业务单号
     * @param e 异常
     */
    private void exceHandler(String businessNo , Throwable e){
        try {
            thereIsNoTransactionControlNoResultService.run(() -> exceptionHandler(businessNo,e));
        } catch (Throwable e1) {
            log.error("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 执行异常处理器时失败", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo, e1);
        }
    }

    /**
     * 加锁
     *
     * @param lockKey   锁
     * @param requestId 请求ID
     */
    private void tryGetDistributedLock(String lockKey, String requestId) {
        //加分布式锁
        boolean isLock;
        try {
            isLock = redisDistributedLockUtils.tryGetDistributedLock(lockKey, requestId, 180);
            log.info("流程类型: [{}] , 当前节点: [{}] , 锁KEY(业务单号+当前节点CODE): [{}] , 加锁成功", getRefType().getName(), getCurrentFlowNode().getNodeName(), lockKey);
        } catch (Throwable e) {
            log.error("流程类型: [{}] , 当前节点: [{}] , 锁KEY(业务单号+当前节点CODE): [{}] , 进行加锁时失败", getRefType().getName(), getCurrentFlowNode().getNodeName(), lockKey, e);
            throw new FlowNodeJobBusinessException("中间件服务异常");
        }
        if (!isLock) {
            throw new FlowNodeJobBusinessException("当前节点正在执行,请勿重复执行");
        }
    }

    /**
     * 释放锁
     *
     * @param lockKey   锁
     * @param requestId 请求ID
     */
    private void releaseDistributedLock(String lockKey, String requestId) {
        try {
            redisDistributedLockUtils.releaseDistributedLock(lockKey, requestId);
            log.info("流程类型: [{}] , 当前节点: [{}] , 锁KEY(业务单号+当前节点CODE): [{}] , 释放锁成功", getRefType().getName(), getCurrentFlowNode().getNodeName(), lockKey);
        } catch (Throwable e) {
            log.error("流程类型: [{}] , 当前节点: [{}] , 锁KEY(业务单号+当前节点CODE): [{}] , 释放锁时失败, 等待过期时间过期", getRefType().getName(), getCurrentFlowNode().getNodeName(), lockKey, e);
        }
    }

    /**
     * 通过
     *
     * @param businessNo   业务单号
     * @param operatorId 操作员ID
     * @param operatorName 操作员名称
     * @param remarks 备注
     * @param data 执行器执行结束后中的额外参数
     */
    protected R pass(String businessNo, String operatorId, String operatorName, String remarks , M data) {
        manualNodeCheck(FlowOptionEnum.PASS, businessNo, operatorId, operatorName, remarks);
        log.info("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 开始执行节点通过指令", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo);
        try {
            //通过
            T nextNode;
            if (getCurrentFlowNode().isStartNode()) {
                nextNode = startNode(businessNo);
            } else {
                nextNode = nextNode(FlowOptionEnum.PASS, businessNo, operatorId,operatorName,remarks);
            }
            R res = pass(businessNo, nextNode, operatorId,operatorName,remarks,data);
            //尝试推送下一个节点任务
            sendNextNodeRun(businessNo, nextNode);
            log.info("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 通过指令执行完成, 响应参数: [{}]", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo, res == null ? "null" : JSON.toJSONString(res));
            return res;
        } catch (FlowNodeJobBusinessException e) {
            log.error("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 节点通过指令执行失败", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo, e);
            throw e;
        } catch (Throwable e) {
            log.error("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 节点通过指令执行失败", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo, e);
            throw new FlowNodeJobBusinessException("内部异常");
        }
    }

    /**
     * 拒绝
     *
     * @param businessNo   业务单号
     * @param operatorId 操作员ID
     * @param operatorName 操作员名称
     * @param remarks 备注
     * @param data 执行器执行结束后中的额外参数
     */
    protected R reject(String businessNo, String operatorId, String operatorName, String remarks , M data) {
        manualNodeCheck(FlowOptionEnum.REJECT, businessNo, operatorId, operatorName, remarks);
        log.info("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 开始执行节点拒绝指令", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo);
        //拒绝
        try {
            T nextNode = nextNode(FlowOptionEnum.REJECT, businessNo, operatorId,operatorName,remarks);
            R res = reject(businessNo, nextNode, operatorId,operatorName,remarks,data);
            //尝试推送下一个节点任务
            sendNextNodeRun(businessNo, nextNode);
            log.info("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 拒绝指令执行完成, 响应参数: [{}]", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo, res == null ? "null" : JSON.toJSONString(res));
            return res;
        } catch (FlowNodeJobBusinessException e) {
            log.error("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 节点拒绝指令执行失败", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo, e);
            throw e;
        } catch (Throwable e) {
            log.error("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 节点拒绝指令执行失败", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo, e);
            throw new FlowNodeJobBusinessException("内部异常");
        }
    }

    /**
     * 驳回
     *
     * @param businessNo   业务单号
     * @param operatorId 操作员ID
     * @param operatorName 操作员名称
     * @param remarks 备注
     * @param data 执行器执行结束后中的额外参数
     */
    protected R refuse(String businessNo, String operatorId, String operatorName, String remarks , M data) {
        manualNodeCheck(FlowOptionEnum.REFUSE, businessNo, operatorId, operatorName, remarks);
        log.info("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] ,  开始执行节点驳回指令", getRefType().getName(), getCurrentFlowNode().getNodeName());
        //驳回
        try {
            T nextNode = nextNode(FlowOptionEnum.REFUSE,businessNo, operatorId,operatorName,remarks);
            R res = refuse(businessNo, nextNode, operatorId,operatorName,remarks,data);
            //尝试推送下一个节点任务
            sendNextNodeRun(businessNo, nextNode);
            log.info("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 拒绝指令执行完成, 响应参数: [{}]", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo, res == null ? "null" : JSON.toJSONString(res));
            return res;
        } catch (FlowNodeJobBusinessException e) {
            log.error("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 节点驳回指令执行失败", getRefType().getName(), getCurrentFlowNode().getNodeName(), e);
            throw e;
        } catch (Throwable e) {
            log.error("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] , 节点驳回指令执行失败", getRefType().getName(), getCurrentFlowNode().getNodeName(), e);
            throw new FlowNodeJobBusinessException("内部异常");
        }
    }



    /**
     * 执行下一个节点任务
     *
     * @param businessNo  业务单号
     * @param nextNode    下一个节点
     */
    private void sendNextNodeRun(String businessNo, T nextNode) {
        //非自动执行，直接退出
        if (!nextNode.isFullyAutomatic()) {
            return;
        }
        //如果下一个节点与当前节点一致则不执行
        //避免递归重复执行
        if (StringUtils.equals(nextNode.getNodeCode(), getCurrentFlowNode().getNodeCode())) {
            return;
        }
        //触发执行下一个任务的事件(当前事务提交之后)
        runAfterTransactionCommitted(() -> FlowNodeJobUtils.fullyAutomaticRun(nextNode,businessNo,null));
    }

    /**
     * 半自动入参校验
     */
    private void manualNodeCheck(FlowOptionEnum option, String businessNo, String operatorId, String operatorName, String remarks) {
        log.info("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] ,  开始进行执行判断操作方法前的校验", getRefType().getName(), getCurrentFlowNode().getNodeName(), getCurrentFlowNode());
        try {
            manualNodeVerification(option, businessNo, operatorId, operatorName, remarks);
        } catch (FlowNodeJobBusinessException e) {
            log.error("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] ,  判断操作方法前的校验,校验失败", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo, e);
            throw e;
        } catch (Throwable e) {
            log.error("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] ,  判断操作方法前的校验,校验失败", getRefType().getName(), getCurrentFlowNode().getNodeName(), businessNo, e);
            throw new FlowNodeJobBusinessException("判断操作方法前的校验,校验失败");
        }
    }

    /**
     * 半自动节点的校验 默认实现
     *
     * @param option       操作结果
     * @param businessNo   业务单号
     * @param operatorId   操作员ID
     * @param operatorName 操作员名称
     * @param remarks      备注
     * @throws FlowNodeJobBusinessException
     */
    @Override
    public void manualNodeVerification(FlowOptionEnum option, String businessNo, String operatorId, String operatorName, String remarks) throws FlowNodeJobBusinessException {

    }

    /**
     * 仅当当前事务提交成功后才执行方法,非事务环境直接执行方法
     *
     * @param runnable 需要执行的方法
     */
    public void runAfterTransactionCommitted(Runnable runnable) {
        if (TransactionSynchronizationManager.isSynchronizationActive()
                && transactionTemplate != null) {
            TransactionSynchronizationManager.registerSynchronization(
                    new TransactionSynchronizationAdapter() {
                        @Override
                        public void afterCompletion(int status) {
                            if (status == TransactionSynchronization.STATUS_COMMITTED) {
                                transactionTemplate.execute(
                                        status1 -> {
                                            runnable.run();
                                            return null;
                                        });
                            }
                        }
                    });
        } else {
            log.warn("流程类型: [{}] , 当前节点: [{}] , 业务单号: [{}] ,  未检测到当前的事务环境,开启非事务执行", getRefType().getName(), getCurrentFlowNode().getNodeName(), getCurrentFlowNode());
            runnable.run();
        }
    }

    /**
     * 异常处理器
     * @param businessNo 业务单号
     * @param e 异常
     */
    @Override
    public void exceptionHandler(String businessNo, Throwable e) {
    }

    /**
     * 获取当前节点的枚举
     *
     * @return
     */
    abstract protected T getCurrentFlowNode();

    /**
     * 获取这个节点的类型
     * 例如： 订单流程， 商户入驻流程
     *
     * @return
     */
    abstract protected RefType getRefType();

    /**
     * 根据节点代码获取节点
     *
     * @param nodeCode 节点代码
     * @return 节点
     */
    abstract protected T getByNodeCode(String nodeCode);

    /**
     * 节点校验
     * <p>
     * 校验当前节点与实际业务的当前入库的节点是否一致
     *
     * @return 是否一致
     */
    abstract protected boolean checkCurrentFlowNode(String businessNo);
}
